/**
 * 贝塞尔动画缓动函数，用于控制动画的速度变化
 */
export const GameHighBesselEases = {
  /**线性变化函数，返回输入值本身*/
  linear: (r: number) => r,
  /**正弦变化函数，使用正弦曲线进行插值*/
  sine: (r: number) => Math.sin(r * (Math.PI / 2)),
  /**平滑步进函数，用于创建平滑的动画效果*/
  easeInOut: (r: number) => ((6 * r - 15) * r + 10) * r * r * r,
  /**加速步进函数，开始时较慢，逐渐加速*/
  easeIn: (r: number) => Math.sqrt(r),
  /**减速步进函数，开始时较快，逐渐减速*/
  easeOut: (r: number) => Math.pow(r, 3),
  /**平方加速步进函数，加速效果更为明显*/
  easeInQuad: (r: number) => r * r,
  /**平方减速步进函数，减速效果更为明显*/
  easeOutQuad: (r: number) => r * (2 - r),
  /**平方平滑步进函数，根据时间进度选择加速或减速*/
  easeInOutQuad: (r: number) => (r < 0.5 ? 2 * r * r : -1 + (4 - 2 * r) * r),
  /**立方加速步进函数，加速效果更加明显*/
  easeInCubic: (r: number) => r * r * r,
  /**立方减速步进函数，减速效果更加明显*/
  easeOutCubic: (r: number) => --r * r * r + 1,
  /**立方平滑步进函数，根据时间进度选择加速或减速*/
  easeInOutCubic: (r: number) =>
    r < 0.5 ? 4 * r * r * r : (r - 1) * (2 * r - 2) * (2 * r - 2) + 1,
};

/**
 * GameHighBessel 类负责管理游戏中的高贝塞尔曲线动画
 * 它使用贝塞尔曲线来平滑地移动相机实体，并支持不同的缓动函数来控制动画速度
 */
export default class GameHighBessel {
  private _timer: number | undefined;
  private _fixedPointPosList: { x: number; y: number; z: number }[] = [];
  private _factorialCache: Map<number, number> = new Map();
  private _onFinishCallback: (() => void) | undefined;
  private _onPlayCallback: (() => void) | undefined;
  private _onUpdateCallback: ((point: GameVector3) => void) | undefined;
  private _progress: number = 0;

  /**
   * 获取贝塞尔当前进度
   * 此属性表示当前进度的值，用于外部获取进度状态
   * @returns {number} 当前进度，范围在0到1之间
   */
  get progress(): number {
    return this._progress;
  }

  /**
   * 设置贝塞尔当前进度
   * 此属性允许设置当前进度值，并确保其在0到1的范围内
   * 当进度值被设置时，根据贝塞尔曲线计算相机的位置，并应用偏移量
   * @param {number} value 要设置的进度值，应在0到1的范围内
   */
  set progress(value: number) {
    // 确保进度值在0到1的范围内
    this._progress = Math.max(0, Math.min(1, value));

    if (this.cameraEntity) {
      // 根据当前进度计算贝塞尔曲线上的点
      const point = this.calculateBezierPoint(this._progress);

      // 根据计算出的点和偏移量设置相机位置
      this.cameraEntity.position.set(
        point.x + this.offset_X,
        point.y + this.offset_Y,
        point.z + this.offset_Z
      );
    }
  }

  /**
   * 延迟时间，用于控制某些操作的执行时机
   */
  public delay: number;

  /**
   * 游戏中的相机实体。
   * 通过贝塞尔曲线可以控制相机的位置和方向。
   */
  public cameraEntity: GameEntity | undefined;

  /**
   * 检查标志，用于可记录贝塞尔路径
   */
  public check: boolean = false;

  /**
   * X轴偏移量，用于调整相机在X轴上的位置
   */
  public offset_X: number;

  /**
   * Y轴偏移量，用于调整相机在Y轴上的位置
   */
  public offset_Y: number;

  /**
   * Z轴偏移量，用于调整相机在Z轴上的位置
   */
  public offset_Z: number;

  /**
   * 构造函数初始化了动画的参数和固定点列表
   * @param config 包含了动画的配置信息，如固定点列表、相机实体、延迟和偏移量
   */
  constructor(config: {
    /**
     * 可以是固定点名称的列表，也可以是表示位置的坐标对象列表。
     * - 固定点：模型名字后加上数字，如“pos-1”、“pos-2”等。
     * - 位置坐标：包含x、y、z三个数值的对象列表。
     */
    fixedPointNameOrPosList: string | { x: number; y: number; z: number }[];

    /**
     * 固定点模型是否不可见。
     */
    fixedPointMeshInvisible?: boolean;

    /**
     * 游戏中的相机实体。
     * 通过贝塞尔曲线可以控制相机的位置和方向。
     */
    cameraEntity?: GameEntity;

    /**
     * 延迟时间（可选）。
     * 用于指定某些操作（如动画或过渡）之前的延迟。
     */
    delay?: number;

    /**
     * 偏移量（可选）。
     * 用于指定相对于某个参考点或目标的位置偏移。
     */
    offset?: [number, number, number];
  }) {
    this.delay = config.delay ?? 0.001;
    [this.offset_X, this.offset_Y, this.offset_Z] = config.offset ?? [0, 0, 0];
    this.cameraEntity = config.cameraEntity;

    if (typeof config.fixedPointNameOrPosList === "string") {
      let i = 1;
      try {
        while (true) {
          const entity = world.querySelector(
            `#${config.fixedPointNameOrPosList}-${i}`
          );
          if (!entity) break;
          entity.meshInvisible = config.fixedPointMeshInvisible ?? true;
          this._fixedPointPosList.push({
            x: entity.position.x,
            y: entity.position.y,
            z: entity.position.z,
          });
          i++;
        }
        if (this._fixedPointPosList.length < 2) {
          throw new Error("固定点列表必须至少包含两个点");
        }
      } catch (error) {
        console.error("选择固定点时出错：", error);
        throw error;
      }
    } else {
      this._fixedPointPosList = config.fixedPointNameOrPosList;
      if (this._fixedPointPosList.length < 2) {
        throw new Error("固定点列表必须至少包含两个点");
      }
    }
  }

  /**
   * 计算阶乘值，使用缓存来提高性能
   * @param n 阶乘的输入值
   * @returns 阶乘结果
   */
  private factorial(n: number): number {
    if (n === 0 || n === 1) return 1;
    if (this._factorialCache.has(n)) return this._factorialCache.get(n)!;
    const result = n * this.factorial(n - 1);
    this._factorialCache.set(n, result);
    return result;
  }

  /**
   * 计算二项式系数，用于贝塞尔曲线计算
   * @param n 二项式的上标
   * @param k 二项式的下标
   * @returns 二项式系数
   */
  private binomialCoefficient(n: number, k: number): number {
    return this.factorial(n) / (this.factorial(k) * this.factorial(n - k));
  }

  /**
   * 计算贝塞尔曲线上的点
   * @param t 曲线上的进度值
   * @param speedFunction 用于调整进度的缓动函数
   * @returns 曲线上的点坐标
   */
  private calculateBezierPoint(
    t: number,
    speedFunction: (r: number) => number = (r) => r
  ): Record<string, number> {
    const adjustedT = speedFunction(t);
    const n = this._fixedPointPosList.length - 1;
    let x = 0,
      y = 0,
      z = 0;

    for (let i = 0; i <= n; i++) {
      const coefficient =
        this.binomialCoefficient(n, i) *
        Math.pow(adjustedT, i) *
        Math.pow(1 - adjustedT, n - i);
      x += this._fixedPointPosList[i].x * coefficient;
      y += this._fixedPointPosList[i].y * coefficient;
      z += this._fixedPointPosList[i].z * coefficient;
    }

    return { x, y, z };
  }

  /**
   * 更新相机实体的粒子颜色
   * @param r 红色分量
   * @param g 绿色分量
   * @param b 蓝色分量
   */
  private updateParticleColor(r: number, g: number, b: number) {
    const color = new GameRGBColor(r / 255, g / 255, b / 255);
    Object.assign(this.cameraEntity, {
      particleRate: 100,
      particleSize: Array(32).fill(10),
      particleLifetime: 100000,
      particleColor: Array(16).fill(color),
    });
  }

  /**
   * 返回固定点坐标列表
   * @returns 固定点列表
   */
  public fixedPointList() {
    return this._fixedPointPosList;
  }

  /**
   * 暂停动画并返回当前进度
   * @returns 当前动画进度
   */
  public pause() {
    if (this._timer) {
      clearInterval(this._timer);
      this._timer = undefined;
    }
    return this._progress;
  }

  /**
   * 设置动画完成时的回调函数
   * @param callback 动画完成时执行的回调函数
   */
  public onFinish(callback: () => void) {
    this._onFinishCallback = callback;
  }
  /**
   * 设置动画开始播放时回调函数
   * @param callback 播放事件触发时调用的回调函数
   */
  public onPlay(callback: () => void) {
    this._onPlayCallback = callback;
  }
  /**
   * 当刷新动画进度时执行的回调函数
   * @param point - 一个当前进度下的数据点，包含当前xyz坐标数值字段
   */
  public onUpdate(callback: (point: GameVector3) => void) {
    this._onUpdateCallback = callback;
  }
  /**
   * 播放动画
   * @param config 动画配置，包括进度、速度和缓动函数
   */
  public play(config?: {
    /**
     * 游戏中的相机实体。
     * 通过贝塞尔曲线可以控制相机的位置和方向。
     */
    cameraEntity?: GameEntity;

    /**
     * 定义动画或过渡效果的进度
     * @type {number}
     */
    progress?: number;

    /**
     * 定义动画或过渡效果的速度
     * @type {number}
     */
    speed?: number;

    /**
     * 定义动画或过渡效果的缓动函数类型
     * @type {keyof typeof GameHighBesselEases}
     */
    ease?: keyof typeof GameHighBesselEases;
  }) {
    if (this._onPlayCallback) this._onPlayCallback.call(this);
    if (config?.cameraEntity) this.cameraEntity = config.cameraEntity;
    this.run(
      config?.progress ?? this._progress,
      config?.speed ?? 0.0001,
      GameHighBesselEases[config?.ease ?? "linear"]
    );
  }

  /**
   * 根据多个贝塞尔曲线的控制点转换贝塞尔曲面
   *
   * @param config.controlPoints - 三维控制点矩阵，定义了曲面的形状
   * @param config.u - 可选参数，U方向（水平方向）的细分段数，决定了曲面在U方向上的细腻程度，默认为10
   * @param config.v - 可选参数，V方向（垂直方向）的细分段数，决定了曲面在V方向上的细腻程度，默认为10
   * @returns 返回一个GameVector3数组，代表生成的曲面顶点
   */
  static createBezierSurfaceFromPoints(config: {
    /** 三维控制点矩阵，定义了曲面的形状 */
    controlPoints: { x: number; y: number; z: number }[][];
    /** U方向（水平方向）的细分段数，决定了曲面在U方向上的细腻程度 */
    u?: number;
    /** V方向（垂直方向）的细分段数，决定了曲面在V方向上的细腻程度 */
    v?: number;
  }): GameVector3[] {
    if (!config.u) config.u = 10;
    if (!config.v) config.v = 10;
    const transformedArray: number[][][] = config.controlPoints.map(
      (subArray) => subArray.map((item) => [item.x, item.y, item.z])
    );
    const binomialCoefficient = (n: number, k: number): number => {
      if (k === 0 || k === n) return 1;
      const binomialCache = new Map<string, number>();
      const cacheKey = `${n},${k}`;
      if (binomialCache.has(cacheKey)) return binomialCache.get(cacheKey)!;
      const result =
        binomialCoefficient(n - 1, k - 1) + binomialCoefficient(n - 1, k);
      binomialCache.set(cacheKey, result);
      return result;
    };

    const bezierPoint = (t: number, points: number[][]): number[] => {
      const n = points.length - 1;
      let result: number[] = [0, 0, 0];
      for (let i = 0; i <= n; i++) {
        const bernstein =
          binomialCoefficient(n, i) * Math.pow(t, i) * Math.pow(1 - t, n - i);
        result = result.map((r, index) => r + bernstein * points[i][index]);
      }
      return result;
    };

    const bezierSurface = (
      u: number,
      v: number,
      controlPoints: number[][][]
    ): number[] => {
      const curvePoints: number[][] = controlPoints.map((curve) =>
        bezierPoint(u, curve)
      );
      return bezierPoint(v, curvePoints);
    };
    const vectorSurface: GameVector3[] = [];
    for (let i = 0; i <= config.u; i++) {
      const u = i / config.u;
      for (let j = 0; j <= config.v; j++) {
        const v = j / config.v;
        const point = bezierSurface(u, v, transformedArray);
        const vector = new GameVector3(point[0], point[1], point[2]);
        vectorSurface.push(vector);
      }
    }
    return vectorSurface;
  }

  /**
   * 更新动画进度
   * @param speed 动画速度
   */
  private updateProgress(speed: number) {
    this._progress = Math.min(1, Math.max(0, this._progress + speed));
  }

  /**
   * 内部方法，用于运行动画
   * @param progress 初始进度
   * @param speed 动画速度
   * @param besselEases 缓动函数
   */
  private run(
    progress: number = 0,
    speed: number = 0.0001,
    besselEases: (r: number) => number = (r) => r
  ) {
    if (this._fixedPointPosList.length < 2) {
      throw new Error("定点列表必须至少包含两个点");
    }
    this.pause();
    this._progress = Math.max(0, Math.min(1, progress));

    this._timer = setInterval(() => {
      this.updateProgress(speed);
      if (this.check && this._progress % 0.05 === 0) {
        let r = Math.round(Math.random() * 255),
          g = Math.round(Math.random() * 255),
          b = Math.round(Math.random() * 255);
        this.updateParticleColor(r, g, b);
      }
      const point = this.calculateBezierPoint(this._progress, besselEases);
      this.cameraEntity?.position.set(
        point.x + this.offset_X,
        point.y + this.offset_Y,
        point.z + this.offset_Z
      );
      if (this._onUpdateCallback)
        this._onUpdateCallback.call(
          this,
          new GameVector3(point.x, point.y, point.z)
        );
      if (this._progress >= 1) {
        this.pause();
        if (this._onFinishCallback) this._onFinishCallback.call(this);
      }
    }, this.delay);
  }
}
